package com.sinosoft.hydra.um.services.component.dataimport;

import cn.com.sinux.spring.utils.*;
import com.sinosoft.hydra.logging.LogModules;
import com.sinosoft.hydra.um.LoginUser;
import cn.com.sinux.spring.commons.SpringUtils;
import com.sinosoft.hydra.um.condition.SearchImportReportCondition;
import com.sinosoft.hydra.um.constant.BaseConstant;
import com.sinosoft.hydra.um.domain.model.ImportConfig;
import com.sinosoft.hydra.um.domain.model.ImportTemplate;
import com.sinosoft.hydra.um.domain.permission.TImportReport;
import cn.com.sinux.spring.exception.BusinessException;
import cn.com.sinux.spring.exception.ServiceException;
import com.sinosoft.hydra.um.repositories.TImportReportDAO;
import com.sinosoft.hydra.um.services.FileService;
import com.sinosoft.hydra.um.services.component.dataimport.resolvers.ValidateResult;
import com.sinosoft.hydra.um.services.component.dataimport.validators.EnumStringValidator;
import com.sinosoft.hydra.um.services.component.dataimport.validators.Validator;
import org.apache.commons.lang.StringUtils;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;
import java.io.*;
import java.lang.reflect.Method;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Created by Administrator on 2015/10/9.
 */
@Service
public class ImportService {
    @Autowired
    private TImportReportDAO tImportReportDAO;
    @Autowired
    private FileService fileService;


    public Page<TImportReport> findImportReportsByKeyCode(final SearchImportReportCondition searchImportReportCondition, Pageable pageable) {
        Specification<TImportReport> tImportTemplateSpecification = SpecificationHelper.buildSpecification(new SpecificationHelper.SpecificationBuilder<TImportReport>() {
            public void fillParam(Root<TImportReport> root, CriteriaBuilder cb, List<Predicate> list) {
                if (StringUtils.isNotBlank(searchImportReportCondition.getImportKey())) {
                    list.add(cb.equal(root.get("importKey").as(String.class), searchImportReportCondition.getImportKey()));
                }
            }
        });
        return tImportReportDAO.findAll(tImportTemplateSpecification, pageable);
    }

    /**
     * Excel返回对象的解析类
     */
    private static class ExcelAnalysis {
        private int cellNumber;
        private String propertyName;
        private CellDataResolver cellDataResolver;
        private List<Validator> validators;
        private boolean lastAll;

        public int getCellNumber() {
            return cellNumber;
        }

        public void setCellNumber(int cellNumber) {
            this.cellNumber = cellNumber;
        }

        public boolean isLastAll() {
            return lastAll;
        }

        public void setLastAll(boolean lastAll) {
            this.lastAll = lastAll;
        }

        public String getPropertyName() {
            return propertyName;
        }

        public void setPropertyName(String propertyName) {
            this.propertyName = propertyName;
        }

        public CellDataResolver getCellDataResolver() {
            return cellDataResolver;
        }

        public void setCellDataResolver(CellDataResolver cellDataResolver) {
            this.cellDataResolver = cellDataResolver;
        }

        public List<Validator> getValidators() {
            return validators;
        }

        public void setValidators(List<Validator> validators) {
            this.validators = validators;
        }

        private static boolean isLastAll(String page) {
            if (StringUtils.isBlank(page)) {
                return false;
            }
            Pattern pattern = Pattern.compile("^\\d+-$", Pattern.CASE_INSENSITIVE);
            Matcher matcher = pattern.matcher(page);
            return matcher.matches();
        }

        private static boolean isNumber(String page) {
            if (StringUtils.isBlank(page)) {
                return false;
            }
            Pattern pattern = Pattern.compile("^\\d+$", Pattern.CASE_INSENSITIVE);
            Matcher matcher = pattern.matcher(page);
            return matcher.matches();
        }

        public static List<ExcelAnalysis> buildExcelAnalysis(String domainProperty) throws Exception {
            String[] properties = StringUtils.split(domainProperty, ";");
            List<ExcelAnalysis> excelAnalysises = new ArrayList<ExcelAnalysis>(properties.length);
            for (String property : properties) {
                String[] po = property.split("=");
                ExcelAnalysis excelAnalysis = new ExcelAnalysis();
                String first = po[0].trim();
                if (!isNumber(first)) {
                    if (!isLastAll(first))
                        throw new ServiceException("模板配置有错，请检查！");
                    first = first.substring(0, first.indexOf("-"));
                    excelAnalysis.setLastAll(true);
                }
                excelAnalysis.setCellNumber(Integer.valueOf(first.trim()));
                excelAnalysis.setPropertyName(po[1].trim());
                excelAnalysis.setCellDataResolver(new CellDataResolver(po[2].trim()));
                if (po.length == 4) {
                    excelAnalysis.setValidators(Validator.createValidatorKeys(po[3].trim()));
                }
                excelAnalysises.add(excelAnalysis);
            }
            return excelAnalysises;
        }

        /**
         * 判断验证中是否有枚举关系
         *
         * @return
         */
        private boolean hasEnumStringValidator() {
            if (ValidatorUtils.isArrayEmpty(this.validators)) {
                return false;
            }
            for (Validator validator : this.validators) {
                if (validator instanceof EnumStringValidator) {
                    return true;
                }
            }
            return false;
        }

        /**
         * 将相关值转换成枚举结果值
         *
         * @param relations
         * @param dataResult
         */
        public void parse(Map<String, String> relations, DataResult dataResult) {
            if (relations.isEmpty() || dataResult == null || !dataResult.isValid() || dataResult.getSource() == null || !hasEnumStringValidator()) {
                return;
            }
            String result = (String) dataResult.getSource();
            if (StringUtils.isBlank(result)) {
                return;
            }
            result = relations.get(result);
            if (StringUtils.isNotBlank(result)) {
                dataResult.setSource(result);
            }
        }
    }

    private Map<String, String> buildEnumStringRelation(String relation) {
        Map<String, String> map = new HashMap<String, String>();
        if (StringUtils.isBlank(relation)) {
            return map;
        }
        String[] properties = StringUtils.split(relation, ";");
        for (String property : properties) {
            String[] po = property.split("=");
            map.put(po[0].trim(), po[1].trim());
        }
        return map;
    }

    /**
     * 批量导入的结果类
     */
    private class TotalAndResult {
        private int total;

        private List<ImportParam.Source> sources;
        private List<String> failureDetails;

        public TotalAndResult(int total, List<ImportParam.Source> sources, List<String> failureDetails) {
            this.total = total;
            this.sources = sources;
            this.failureDetails = failureDetails;
        }

        public List<String> getFailureDetails() {
            return failureDetails;
        }

        public int getTotal() {
            return total;
        }

        public List<ImportParam.Source> getSources() {
            return sources;
        }
    }

    /**
     * 通过excel读取获取导入的业务对象
     *
     * @param fileName       导入的文件名
     * @param inputStream    导入的数据流
     * @param domainClaze    导入的数据的模型类
     * @param domainProperty 导入数据的属性参数
     * @return 解析结果
     */
    private TotalAndResult createImportObject(String fileName, InputStream inputStream,
                                              final Class domainClaze, String domainProperty, String enumStringRelation) {
        final List<ExcelAnalysis> analysises = new ArrayList<ExcelAnalysis>();
        try {
            analysises.addAll(ExcelAnalysis.buildExcelAnalysis(domainProperty));
        } catch (Exception e) {
            throw new BusinessException("{cuo.wu.de.doamin.mo.ban}");
        }
        final List<ImportParam.Source> sources = new LinkedList<ImportParam.Source>();
        final List<String> failureDetails = new LinkedList<String>();
        ExcelReader excelReader = new ExcelReader();
        final Map<String, String> relations = buildEnumStringRelation(enumStringRelation);
        List<Row> rows = excelReader.readData(inputStream, fileName);
        int total = rows.size();
        for (Row row : rows) {
            try {
                Object domain = domainClaze.newInstance();//模型实体
                boolean validate = true;
//                    对analysises最后一个进行检查，判断是否是变动数，是的话叠加数据
                ExcelAnalysis analysis = analysises.get(analysises.size() - 1);
                if (analysis.isLastAll()) {
                    for (int i = analysis.getCellNumber() + 1; i < row.getLastCellNum(); i++) {
                        ExcelAnalysis newObj = new ExcelAnalysis();
                        CloneUtils.cloneProperty(analysis, newObj);
                        newObj.setCellNumber(i);
                        analysises.add(newObj);
                    }
                }
                for (ExcelAnalysis excelAnalysis : analysises) {
                    Cell cell = row.getCell(excelAnalysis.getCellNumber());
                    DataResult result = excelAnalysis.getCellDataResolver().resolveCell(ExcelReader.getCellData(cell));
                    validate = result.isValid();
                    if (!validate) {
                        failureDetails.add("第" + row.getRowNum() + "行数据转换失败，第" + (excelAnalysis.getCellNumber() + "列数据不能转换成目标类型，失败原因是" + result.getError()));
                        LoggerFactory.getLogger(LogModules.STD_LOGGER).warn("第{}行数据转换失败，第{}列数据失败,失败原因{}", row.getRowNum(), excelAnalysis.getCellNumber(), result.getError());
                        break;
                    }
                    if (ValidatorUtils.isArrayNotEmpty(excelAnalysis.getValidators())) {
                        for (Validator validator : excelAnalysis.getValidators()) {
                            ValidateResult validateResult = validator.validate(result.getSource());
                            if (!validateResult.isValid()) {
                                validate = false;
                                failureDetails.add("第" + row.getRowNum() + "行数据验证失败， 第" + (excelAnalysis.getCellNumber()) + "列数据不合法，原因是" + validateResult.getErrors());
                                LoggerFactory.getLogger(LogModules.STD_LOGGER).warn("第{}行数据验证失败，第{}列数据异常，异常原因{}",
                                        row.getRowNum(), excelAnalysis.getCellNumber(), validateResult.getErrors());
                            }
                        }
                    }
                    if (!validate) {
                        break;
                    }
                    excelAnalysis.parse(relations, result);
                    ConvertUtils.buildProperty(domain, excelAnalysis.getPropertyName(), result.getSource());
                }
                if (validate) {
                    sources.add(new ImportParam.Source(row.getRowNum(), domain));
                }
            } catch (Exception e) {
                e.printStackTrace();
                failureDetails.add("第" + row.getRowNum() + "行数据加载异常，异常原因是" + e.getMessage());
                LoggerFactory.getLogger(LogModules.STD_LOGGER).error("加载数据异常{}", e.getMessage());
            }
        }
        return new TotalAndResult(total, sources, failureDetails);
    }

    /**
     * 执行导入（不加事务）
     *
     * @param key      导入的模板的编码
     * @param fileName 文件名称
     * @param filePath 模板流
     * @return 0是导入数据的总数 1是导入成功的总数
     */
    public TImportReport doImport(String key, String fileName, String filePath, final ImportParam importParam) {
        LoginUser loginUser = importParam.getLoginUser();
        long time = System.currentTimeMillis();
        LoggerFactory.getLogger(LogModules.STD_LOGGER).debug("用户{}尝试导入数据，导入开始{}", importParam.getLoginUser().getUserName());
        ImportTemplate importTemplate = ImportTemplate.valueOf(key);
        if (importTemplate == null) {
            throw new ServiceException("错误的参数模板");
        }
        InputStream inputStream = null;
        try {
            inputStream = new FileInputStream(fileService.getRelativePath() + filePath);
        } catch (FileNotFoundException e) {
            throw new ServiceException("目标文件不存在");
        }
        //获取domain
        Class _claze = null;
        try {
            _claze = Class.forName(importTemplate.getDomainName());
        } catch (Exception e) {
            try {
                inputStream.close();
            } catch (IOException ex) {
                e.printStackTrace();
            }
            throw new ServiceException("{cuo.wu.de.domain.mo.ban}");
        }
        //获取执行方法
        Method persistenceMethods = null;
        Object service = null;
        int lastDomain = importTemplate.getPersistenceMethod().lastIndexOf(".");
        String servie = importTemplate.getPersistenceMethod().substring(0, lastDomain),
                methodName = importTemplate.getPersistenceMethod().substring(lastDomain + 1);
        try {
            //method =

            service = SpringUtils.getBean(Class.forName(servie));
        } catch (ClassNotFoundException e) {
            LoggerFactory.getLogger(LogModules.STD_LOGGER).error("没有获取到执行的service，不能导入{}", servie);
            throw new ServiceException("没有获取到制定方法");
        }
        if (service == null) {
            throw new ServiceException("导入失败");
        }
        Method[] methods = service.getClass().getMethods();
        for (Method method : methods) {
            if (method.getName().equals(methodName)) {
                persistenceMethods = method;
                break;
            }
        }
        if (persistenceMethods == null) {
            LoggerFactory.getLogger(LogModules.STD_LOGGER).error("没有获取到执行的service方法，不能导入{}", importTemplate.getPersistenceMethod());
            throw new ServiceException("没有获取到制定方法");
        }
        TotalAndResult totalAndResult = createImportObject(fileName, inputStream, _claze, importTemplate.getDomainProperty(), importTemplate.getDomainEnum());

        List<String> failureDetails = totalAndResult.getFailureDetails();
        TImportReport tImportReport = new TImportReport();
        tImportReport.setImportKey(importTemplate.getKeyCode());
        tImportReport.setImportTime(new Date());
        tImportReport.setImportUser(importParam.getLoginUser());
        tImportReport.setTotalNum(totalAndResult.getTotal());
        tImportReport.setFailureNum(failureDetails.size());
        importParam.setSources(totalAndResult.getSources());
        if (failureDetails.isEmpty() || !importTemplate.isErrorStop()) {
            try {
                persistenceMethods.invoke(service, importParam);
            } catch (Exception ex) {
                LoggerFactory.getLogger(LogModules.STD_LOGGER).info("系统调用持久方法进行持久化出错");
                tImportReport.setResult(BaseConstant.Result.RESULT_FAILURE.name());
                failureDetails.add("系统执行数据保存失败，失败原因" + ex.getCause().getMessage());
            }
        }
        int failureNum = tImportReport.getFailureNum();
        tImportReport.setFailureNum(failureNum + importParam.getFailures().size());
        failureDetails.addAll(importParam.getFailures());
        LoggerFactory.getLogger(LogModules.STD_LOGGER).debug("用户{}导入数据结束，耗时{}毫秒，共导入数据{}条，成功{}条", loginUser.getUserName(), System.currentTimeMillis() - time, totalAndResult.getTotal(), failureDetails.size());
        tImportReport.setResult(BaseConstant.Result.RESULT_SUCCESS.name());
        if (!failureDetails.isEmpty()) {
//            写入错误报告
            tImportReport.setImportReportFile(printReport(failureDetails));
            tImportReport.setResult(BaseConstant.Result.RESULT_FAILURE.name());
        }
        tImportReportDAO.save(tImportReport);
        return tImportReport;
    }

    /**
     * 查询批量导出报告
     *
     * @param searchImportReportCondition
     * @return
     */
    public List<TImportReport> findImportReports(final SearchImportReportCondition searchImportReportCondition) {
        return findImportReports(searchImportReportCondition, null).getContent();
    }

    /**
     * 查询批量导出报告
     *
     * @param searchImportReportCondition
     * @return
     */
    public Page<TImportReport> findImportReports(SearchImportReportCondition searchImportReportCondition, Pageable pageable) {
        final SearchImportReportCondition condition =
                searchImportReportCondition == null
                        ? new SearchImportReportCondition()
                        : searchImportReportCondition;
        Specification<TImportReport> specification = SpecificationHelper.buildSpecification(new SpecificationHelper.SpecificationBuilder<TImportReport>() {

            public void fillParam(Root<TImportReport> root, CriteriaBuilder cb, List<Predicate> list) {
                if (StringUtils.isNotBlank(condition.getImportKey())) {
                    list.add(cb.equal(root.get("importKey").as(String.class), condition.getImportKey()));
                }
            }
        });
        return tImportReportDAO.findAll(specification, pageable);
    }

    private String printReport(List<String> failures) {
        StringBuilder stringBuffer = new StringBuilder();
        for (String failure : failures) {
            stringBuffer.append(failure).append("\n");
        }
        String relativePath = fileService.getRelativePath();
        String path = fileService.createPath(relativePath, "test.txt");
        try {
            FileUtils.writeStringToFile(new File(relativePath + path), stringBuffer.toString(), "UTF-8");
        } catch (IOException e) {
            LoggerFactory.getLogger(LogModules.STD_LOGGER).error("生成报告失败");
        }
        return path;
    }


    public ImportConfig buildConfig(String key) {
        ImportTemplate importTemplate = ImportTemplate.valueOf(key);
        if (importTemplate == null) {
            throw new ServiceException("模板不存在");
        }
        try {
            JAXBContext context = JAXBContext.newInstance(ImportConfig.class);
            Unmarshaller unmarshaller = context.createUnmarshaller();
            ImportConfig importConfig = (ImportConfig) unmarshaller.unmarshal(new File(fileService.getRelativePath() + importTemplate.getDesPath()));
            importConfig.setFilePath(importTemplate.getAttachment());
            return importConfig;
        } catch (Exception e) {
            throw new ServiceException("模板解析出错");
        }
    }
}
